package tools;

import java.util.ArrayList;
import java.util.Random;

import org.apache.commons.math.special.Gamma;

import data.set.OfflineSet;
import data.set.OfflineSetIterator;
import data.set.implementation.OfflineSet_Memory;
import data.vectors.DoubleVector;
import data.vectors.DoubleVectorIterator;
import data.vectors.implementation.DoubleVector_Sparse_Memory;

public class MathematicTool {
	
	public static Random rand = new Random();
	
	public static void normalize(Double[] m){
		Double s=0.0;
		s = sum(m);
		if (s==0)
			s=(double) m.length;
//		System.out.println("in normalization, the sum:"+s);
		for (int i=0;i<m.length;i++){
			m[i]/=s;
		}
	}
	public static void normalize(Double[][] m){
		for (int i=0;i<m.length;i++){
//			System.out.println(i+"th line");
			normalize(m[i]);
		}
	}
	public static void normalizeJD(Double[][] m){
		double t = 0.;
		for (int i=0;i<m.length;i++){
			for (int j=0;j<m[0].length;j++){
				t+=m[i][j];
			}
		}
		if(t==0) // supprimer la normalisation en cas de 0
			t=1;
		for (int i=0;i<m.length;i++){
			for (int j=0;j<m[0].length;j++){
				m[i][j] /= t;
			}
		}
	}
	public static void randInit(Double[] m){
		for (int i=0;i<m.length;i++){
			m[i]=rand.nextDouble();
			while(m[i]==0){
				m[i]=rand.nextDouble();
			}
		}
	}
	public static void randInit(Double[][] m){
		for(int i=0; i<m.length;i++)
			randInit(m[i]);
	}
	// vector*vector
	public static Double vectorMultiple(Double[] m1, Double[] m2){
		if (m1.length!=m2.length){
			System.err.println("size of vector don't march");
			return null;
		}
		Double r = 0.0;
		for (int i=0;i<m1.length;i++)
			r+=m1[i]*m2[i];
		return r;
	}
	// matrix*matrix
	public static Double[][] vectorMultiple(Double[][] m1, Double[][] m2){
		if (m1[0].length!=m2.length){
			System.err.println("size of matrix don't march");
			return null;
		}
		Double[][] r = zeros(m1.length,m2[0].length);
		for (int i=0; i<m1.length; i++){
			for (int j=0; j<m2.length; j++){
				for (int x=0;x<m1[0].length;x++){
					r[i][j]+=m1[i][x]*m2[x][j];
				}
			}
		}
		
		return r;
	}
	// vector * sparse_vector
	public static Double vectorMultiple(Double[] m1, DoubleVector m2){
		Double r = 0.0;
		DoubleVectorIterator iter = m2.getNonNullIterator();
		while(iter.hasNext()){
			iter.next();
			int key = iter.getCurrentFeature();
			r+=m1[key]*iter.getCurrentValue();
		}
		return r;
	}
	//sparse_vector * sparse_vector
	public static Double vectorMultiple(DoubleVector m1, DoubleVector m2){
		Double r = 0.0;
		DoubleVectorIterator iter = m2.getNonNullIterator();
		while(iter.hasNext()){
			iter.next();
			int key = iter.getCurrentFeature();
			Double v1 = m1.getValue(key);
			if (v1!=0.0)
				r+=v1*iter.getCurrentValue();
		}
		return r;
	}
	// matrix * sparse_vector
	public static Double[] vectorMultiple(Double[][] m1, DoubleVector m2){
		Double[] r = new Double[m1.length];
		for (int i=0;i<m1.length;i++){
			r[i]=vectorMultiple(m1[i], m2);
		}
		return r;
	}
	// matrix*sparse_matrix
	public static Double[][] vectorMultiple(Double[][] m1, DoubleVector[] m2){
//		System.out.println("\t\t\tmatrix*sparse matrix ...");
		Double[][] r = new Double[m2.length][m1.length];
		for (int i=0;i<m2.length; i++){
			r[i]=vectorMultiple(m1, m2[i]);
		}
		return r;
	}
	// sparse_matrix*matrix (a*b * b*c) => c*a, a is the complete size of saprse_matrix
	public static Double[][] vectorMultiple(DoubleVector[] m1, Double[][] m2, int numV){
		if (m1.length!=m2.length)
			return null;
//		System.out.println("\t\t\tsparse matrix*matrix ...");
		Double[][] r = zeros(m2[0].length,numV);
		for (int k=0;k<m2.length;k++){
			// for each document
			DoubleVectorIterator iter = m1[k].getNonNullIterator();
			while(iter.hasNext()){
				// for each feature
				iter.next();
				int featureId = iter.getCurrentFeature();
				double featureValue = iter.getCurrentValue();
				for (int j=0;j<m2[0].length;j++){
					r[j][featureId] += featureValue*m2[k][j];
				}
			}
		}
		
//		for (int i=0;i<numV;i++){
//			// for each word or 
//			for (int j=0;j<m2[0].length;j++){
//				// for each topic or 
//				Double v;
//				for (int k=0;k<m2.length;k++){
//					// for each document
//					v = m1[k].getValue(i);
//					if (v!=0){
////						System.out.println("value of r:"+r[j][i]+" value of v:"+v+" value of m2:"+m2[k][j]);
//						r[j][i]+=v*m2[k][j];
//					}
//				}
//			}
//		}
		return r;
	}
	public static DoubleVector arrayMultiple(Double[] m1, DoubleVector m2){
		DoubleVector_Sparse_Memory r = new DoubleVector_Sparse_Memory();
		DoubleVectorIterator iter = m2.getNonNullIterator();
		while(iter.hasNext()){
			iter.next();
			int key = iter.getCurrentFeature();
			r.setValue(key, m1[key]*iter.getCurrentValue());
		}
		return r;
	}
	public static Double[] arrayMultiple(Double[] m1, Double[] m2){
		if (m1.length!=m2.length){
			System.err.println("size of vector don't march");
			return null;
		}
		Double[] r = new Double[m1.length];
		for (int i=0;i<m1.length;i++)
			r[i]=m1[i]*m2[i];
		return r;
	}
//	public static DoubleVector_Sparse_Memory arrayDivise(DoubleVector_Sparse_Memory v1, DoubleVector_Sparse_Memory v2){
//		if (v1.size()!=v2.size())
//			return null;
//		DoubleVector_Sparse_Memory r = new DoubleVector_Sparse_Memory();
//		DoubleVectorIterator iter = v1.getNonNullIterator();
//		while(iter.hasNext()){
//			iter.next();
//			int key = iter.getCurrentFeature();
//			Double v = v2.getValue(key);
//			if (v==null)
//				return null;
//			r.setValue(key, iter.getCurrentValue()/v);
//		}
//		return r;
//	}
	public static Double[][] arrayMultiple(Double[][] m1, Double[][] m2){
		Double[][] r = zeros(m1.length, m1[0].length);
		for (int i=0;i<m1.length;i++){
			for (int j=0; j<m1[0].length; j++){
				r[i][j] = m1[i][j]*m2[i][j];
			}
		}
		return r;
	}
	public static Double[][] arrayMultiple(Double[][] m1, Double m2){
		Double[][] r = zeros(m1.length, m1[0].length);
		for (int i=0;i<m1.length;i++){
			for (int j=0; j<m1[0].length; j++){
				r[i][j] = m1[i][j]*m2;
			}
		}
		return r;
	}
	public static Double[][] arrayAdd(Double[][] m1, Double[][] m2){
		Double[][] r = zeros(m1.length, m1[0].length);
		for (int i=0;i<m1.length;i++){
			for (int j=0; j<m1[0].length; j++){
				r[i][j] = m1[i][j]+m2[i][j];
			}
		}
		return r;
	}
	
	public static Double[] arrayAdd(Double[] m1, Double[] m2){
		Double[] r = zeros(m1.length);
		for (int i=0;i<m1.length;i++){
			r[i] = m1[i]+m2[i];
		}
		return r;
	}
	
	public static Double[] sum(Double[][] m){
		Double[] r = new Double[m.length];
		for (int i=0;i<m.length;i++){
			r[i]=0.0;
			for (int j=0;j<m[0].length;j++){
				r[i]+=m[i][j];
			}
		}
		
		return r;
	}
	public static Double sum(Double[] m){
		Double r=0.0;
		for (int i=0; i<m.length; i++){
			r+=m[i];
		}
		return r;
	}
	public static int sum(int[] m){
		int r=0;
		for (int i=0; i<m.length; i++){
			r+=m[i];
		}
		return r;
	}
	public static Double[] sum2(Double[][] m){
		Double[] r = new Double[m[0].length];
		for (int i=0;i<m[0].length;i++){
			r[i]=0.0;
			for (int j=0;j<m.length;j++){
				r[i]+=m[j][i];
			}
		}
		
		return r;
	}
	public static Double[] getColumn(Double[][] m, int index){
		Double[] r = new Double[m.length];
		for (int i=0;i<m.length;i++)
			r[i]=m[i][index];
		return r;
	}
	
	public static Pair<Double, Integer> max(DoubleVector_Sparse_Memory vec){
		double v = -100000;
		int index = -1;
		DoubleVectorIterator iter = vec.getNonNullIterator();
		while(iter.hasNext()){
			iter.next();
			double v2 = iter.getCurrentValue();
			int index2 = iter.getCurrentFeature();
			if (v2>v){
				index = index2;
				v = v2;
			}
		}
		
		return new Pair<Double, Integer>(v, index);
	}
	public static Pair<Double, Integer> max(Double[] vec){

		double v = -100000;
		int index = -1;
		for (int i=0; i<vec.length; i++){
			Double v2 = vec[i];
			if (v2>v){
				index = i;
				v = v2;
			}
		}
		
		return new Pair<Double, Integer>(v, index);
	}
	
	/*
	 * order descend
	 */
	public static ArrayList<Pair<Double, Integer>> sort(Double[] vec){
		ArrayList<Pair<Double, Integer>> r = new ArrayList<Pair<Double, Integer>>();
		for (int i=0;i<vec.length;i++){
			r.add(new Pair<Double, Integer>(vec[i],i));
		}
		
		for (int i=0;i<vec.length-1;i++){
			Pair<Double, Integer> iem = r.get(i);
			for (int j=i+1;j<vec.length;j++){
				Pair<Double, Integer> tem = r.get(j);
				if (tem.getX()>iem.getX()){
					r.remove(j);
					r.add(i,tem);
					iem = tem;
				}
			}
		}
		return r;
	}
	public static ArrayList<Pair<Integer, Integer>> sort(Integer[] vec){
		ArrayList<Pair<Integer, Integer>> r = new ArrayList<Pair<Integer, Integer>>();
		for (int i=0;i<vec.length;i++){
			r.add(new Pair<Integer, Integer>(vec[i],i));
		}
		
		for (int i=0;i<vec.length-1;i++){
			Pair<Integer, Integer> iem = r.get(i);
			for (int j=i+1;j<vec.length;j++){
				Pair<Integer, Integer> tem = r.get(j);
				if (tem.getX()>iem.getX()){
					r.remove(j);
					r.add(i,tem);
					iem = tem;
				}
			}
		}
		return r;
	}
	public static ArrayList<Pair<Double, Integer>> sort(OfflineSet<Double> set){
		ArrayList<Pair<Double, Integer>> r = new ArrayList<Pair<Double, Integer>>();
		OfflineSetIterator<Double> iter = set.inputsetiterator();
		while(iter.hasNext()){
			iter.next();
			int id = iter.getCurrentId();
			double v = iter.getCurrentObject();
			r.add(new Pair<Double, Integer>(v,id));
		}
		
		
		for (int i=0;i<set.size()-1;i++){
			Pair<Double, Integer> iem = r.get(i);
			for (int j=i+1;j<set.size();j++){
				Pair<Double, Integer> tem = r.get(j);
				if (tem.getX()>iem.getX()){
					r.remove(j);
					r.add(i,tem);
					iem = tem;
				}
			}
		}
		return r;
	}
	public static ArrayList<Pair<Double, Integer>> sortAbs(OfflineSet<Double> set){
		ArrayList<Pair<Double, Integer>> r = new ArrayList<Pair<Double, Integer>>();
		OfflineSetIterator<Double> iter = set.inputsetiterator();
		while(iter.hasNext()){
			iter.next();
			int id = iter.getCurrentId();
			double v = iter.getCurrentObject();
			r.add(new Pair<Double, Integer>(v,id));
		}
		
		
		for (int i=0;i<set.size()-1;i++){
			Pair<Double, Integer> iem = r.get(i);
			for (int j=i+1;j<set.size();j++){
				Pair<Double, Integer> jem = r.get(j);
				if (Math.abs(jem.getX())>Math.abs(iem.getX())){
					r.remove(j);
					r.add(i,jem);
					iem = jem;
				}
			}
		}
		return r;
	}
	public static Double[][] zeros(int l, int c){
		return init(l,c,0);
	}
	public static Double[] zeros(int l){
		return init(l, 0);
	}
	public static Double[][] init(int l, int c, double n){
		Double[][] r = new Double[l][c];
		for (int i=0;i<l;i++){
			for (int j=0;j<c;j++){
				r[i][j]=n;
			}
		}
		return r;
	}
	public static Double[] init(int l, double n){
		Double[] r = new Double[l];
		for(int i=0;i<l;i++){
			r[i] = n;
		}
		return r;
	}
	public static DoubleVector copy(DoubleVector v){
		DoubleVector_Sparse_Memory r = new DoubleVector_Sparse_Memory(v.size());
		DoubleVectorIterator iter = v.getNonNullIterator();
		while(iter.hasNext()){
			iter.next();
			int id = iter.getCurrentFeature();
			double value = iter.getCurrentValue();
			r.setValue(id, value);
		}
		
		return r;
	}
	public static Double[] copy(Double[] vec){
		Double[] r = new Double[vec.length];
		for (int i=0; i<vec.length; i++){
			r[i] = vec[i];
		}
		
		return r;
	}
	public static Double[][] copy(Double[][] m){
		Double[][] r = new Double[m.length][m[0].length];
		for (int i=0; i<m.length; i++){
			for (int j=0; j<m[0].length; j++){
				r[i][j] = m[i][j];
			}
		}
		
		return r;
	}
	
	public static Double[][] exp(Double[][] m){
		Double[][] r = new Double[m.length][m[0].length];
		for (int i=0; i<m.length; i++){
			r[i] = exp(m[i]);
		}
		return r;
	}
	public static Double[] exp(Double[] m){
		Double[] r = new Double[m.length];
		for (int i=0; i<m.length; i++)
			r[i]=Math.exp(m[i]);
		return r;
	}
	
	public static void digammaSum(Double[][] in, Double[][] out){
		// gamma numD*numT -> digamma(numD*numT)-digamma(numD)
		// phi numT*numV -> digamma(numT*numV)-digamma(numT)
		for (int i=0; i<in.length; i++){
			digammaSum(in[i], out[i]);
		}
	}
	public static void digammaSum(Double[] in, Double[] out){
		double v=0;
		for (int i=0; i<in.length; i++){
			v+=in[i];		
		}
		v = Gamma.digamma(v);
		for (int i=0; i<in.length; i++){
			out[i]=Gamma.digamma(in[i])-v;		
		}
	}
	
	public static void digamma(Double[][] in, Double[][] out){
		for (int i=0; i<in.length; i++){
			for (int j=0; j<in[0].length; j++){
				out[i][j]=Gamma.digamma(in[i][j]);
			}
		}
	}
	
	/*
	 *  not implemented
	 */
	public static Double[][] dirichletSample(Double[] alpha, int num){
		Double[][] r = new Double[alpha.length][num];
		
		return r;
	}
	
	public static double diffVec(Double[] v1, Double[] v2){
		double a=0,b=0;
		for (int i=0; i<v1.length; i++){
			a+=Math.pow(v1[i]-v2[i], 2);
			b+=Math.pow(v1[i], 2);
		}
		return a/b;
	}
	public static Double[][] inverse(Double[][] m){
		Double[][] r = new Double[m[0].length][m.length];
		for(int i=0; i<m.length; i++){
			for (int j=0; j<m[0].length; j++){
				r[j][i] = m[i][j];
			}
		}
		
		return r;
	}
	public static double cov(Double[][] a, Double[][] b){
		double r=0;
		for (int i=0; i<a.length; i++)
			for (int j=0; j<a[0].length; j++)
				r+=Math.pow((a[i][j]-b[i][j]), 2);
		
		return r;
	}
	public static double norm(Double[][] a){
		double r=0;
		for (int i=0; i<a.length; i++)
			for (int j=0; j<a[0].length; j++)
				r+=Math.pow(a[i][j], 2);
		return r;
	}
	public static void smoothSmallValue(Double[][] t){
		double threshold = 1e-150;
		for (int i=0; i<t.length; i++){
			for(int j=0; j<t[0].length; j++){
				t[i][j] = Math.max(t[i][j], threshold);
			}
		}
//		normalize(t);
	}
	
	public static <T> OfflineSet<T> combine(OfflineSet<T> set1, OfflineSet<T> set2){
		OfflineSet_Memory<T> r = new OfflineSet_Memory<T>();
		OfflineSetIterator<T> iter = set1.inputsetiterator();
		while(iter.hasNext()){
			iter.next();
			r.add(iter.getCurrentId(), iter.getCurrentObject());
		}
		iter = set2.inputsetiterator();
		while(iter.hasNext()){
			iter.next();
			r.add(iter.getCurrentId(), iter.getCurrentObject());
		}
		
		return r;
	}
	public static double mean(Double[] t){
		return sum(t)/t.length;
	}
	public static double mean(int[] t){
		return ((double) sum(t))/t.length;
	}
	
	public static <T> OfflineSet<T> sameId(OfflineSet<T> from, OfflineSet<T> to){
		OfflineSet_Memory<T> r = new OfflineSet_Memory<T>();
		OfflineSetIterator<T> iter = from.inputsetiterator();
		while(iter.hasNext()){
			iter.next();
			int id = iter.getCurrentId();
			r.add(id, to.getObject(id));
		}
		
		return r;
	}
	public static void main(String args[]){

//		int size = 10;
//		Double[] list = new Double[size];
//		MatrixOperation.randIni(list);
//		
//		for (int i=0;i<size;i++){
//			System.out.print(" "+list[i]);
//		}
//		
//		System.out.println("\nafter sort");
//		ArrayList<Pair<Double, Integer>> sort = MatrixOperation.sort(list);
//		for (int i=0;i<size;i++){
//			Pair<Double, Integer> pair = sort.get(i);
//			System.out.println("value:"+pair.getX()+" , index:"+pair.getY());
//		}
		
//		Double[][] test = zeros(3,3);
//		for (int i=0;i<3;i++){
//			for (int j=0;j<3;j++){
//				System.out.println("("+i+","+j+")="+test[i][j]);
//			}
//		}
		Object o = new Double[5][5];
		if (o instanceof Double[][])
			System.out.println("is double table");
		if (o instanceof Double)
			System.out.println("is double");
		if (o instanceof Double[])
			System.out.println("is double vector");
	}
	public static void randnInit(Double[] doubles) {
		for(int i=0; i<doubles.length; i++)
			doubles[i] = rand.nextGaussian();
		
	}
	
	
	public static void randnInit(Double[][] m) {
		for(int i=0; i<m.length; i++)
			for(int j=0; j<m[0].length; j++)
			m[i][j] = rand.nextGaussian();
	}	
}
